/*******************************************************************************
 * Copyright (c) 2009 IBM Corporation and others
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 * 
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.aseclipse.osgi.classes {
    
    import org.aseclipse.core.runtime.registry.classes.ExtensionRegistry;
    import org.aseclipse.core.runtime.registry.classes.ExtensionTracker;
    import org.aseclipse.core.runtime.registry.classes.ExtensionsParser;
    import org.aseclipse.core.runtime.registry.classes.Factory;
    import org.aseclipse.core.runtime.registry.classes.IExtensionTracker;
    import org.aseclipse.osgi.Bundle;
    import org.aseclipse.osgi.BundleContext;
    import org.aseclipse.osgi.BundleFramework;
    import org.aseclipse.osgi.FrameworkEvent;
    import org.aseclipse.osgi.loader.BundleConstants;
    
    import flash.events.EventDispatcher;
    import flash.utils.Dictionary;
    
    import mx.collections.ArrayCollection;

public class BundleFrameworkImpl extends EventDispatcher implements BundleFramework {

	private var installOrderBundles:ArrayCollection = new ArrayCollection();
	private var dependentBundles:Dictionary = new Dictionary();
	private var currentBundleId:int = 0;

	private var  bundleCache:Dictionary = new Dictionary();

    /** The ServiceRegistry */
	//private var serviceRegistry:ServiceRegistry;
	 
	private var factory:Factory;
	
	private var extensionParser:ExtensionsParser;
	
	private var extensionRegistry:ExtensionRegistry;
    private var extensionTracker:IExtensionTracker;
    private var bundlelifecyclelistener:EventDispatcher;
    
    // for bundle install service
    private var bundleInstallService:BundleInstallService;
    private var bundleResolveService:BundleResolveService;
    private var bundleStartService:BundleStartService;
    
    private var state:int;
    private static const STARTING:int = 0;
    private static const STARTED:int = 1;
    	
	public function BundleFrameworkImpl() {
	    super();
	    factory = new Factory();
	    //serviceRegistry = new ServiceRegistry(this);
	    extensionRegistry = factory.createRegistry();
	    extensionParser = new ExtensionsParser(factory);
	    extensionTracker = new ExtensionTracker(extensionRegistry);
	    bundlelifecyclelistener = new EventDispatcher();
	    bundleInstallService = new BundleInstallService(this, extensionParser);
	    bundleResolveService = new BundleResolveService(this);
	    bundleStartService = new BundleStartService(this);
	   
	    state = STARTING;
	}
    
    public function getExtensionRegistry():ExtensionRegistry {
        return extensionRegistry;
    }
    
    public function getExtensionTracker():IExtensionTracker {
        return extensionTracker;
    }

	/* public function getServiceRegistry():ServiceRegistry {
		return serviceRegistry;
	} */
	
    public function getBundleDispatcher():EventDispatcher {
        return bundlelifecyclelistener;
    }
    
    /******
    * When starting queue is empty at the first time, OSGi framework is startup.
    */ 
    public function notifyEmptyStartQueue():void {
        if(state == STARTING) {
            state = STARTED;
            this.dispatchEvent(new FrameworkEvent(FrameworkEvent.STARTED));
        } else {
            trace("BundleFramework: it is not the first time of emptying starting queue");
        }
    }
    /*****
    * put bundle into cache when installing it, and remember installing order
    */ 
    public function addBundle(jsBundle:Bundle):void {
        var name:String = jsBundle.getSymbolicName();
        installOrderBundles.addItem(name);
        bundleCache[name] = jsBundle;
        currentBundleId++;  // ?
        // set dependent relationships
        for each(var item:String in jsBundle.getRequires()) {
            var array:Array = dependentBundles[item] as Array;
            if(array == null) {
                array = new Array();
                dependentBundles[item] = array;
            }
            array.push(name);
        }
        // push into extensionRegistry
        extensionRegistry.add((jsBundle as BundleImpl).getPluginModel());
    }
    
	/**
	 * start to install a bundle
	 */
	public function installBundle(pluginId:String):void {
		if (pluginId == null)
			throw new Error("pluginId cannot be null"); //$NON-NLS-1$
		// key is defined in BundleInstallService
		bundleInstallService.install({pluginId:pluginId, loadType:BundleConstants.PATTERN_PLUGIN});
	}
	/**
	 * start to install all bundles which extend specified bundle
	 */
	public function installExtenderBundles(pluginId:String):void {
		if (pluginId == null)
			throw new Error("pluginId cannot be null"); //$NON-NLS-1$
		var bundle:BundleImpl = getBundle(pluginId) as BundleImpl;
		if(bundle != null) {
		    // if no extension point, then no extender
		    var points:Array = bundle.getPluginModel().getExtensionPoints();
		    if(points.length == 0) {
		        // DO NOTHING
		        bundle.extendersReady = true;
		    } else {
		        bundleInstallService.install({pluginId:pluginId, loadType:BundleConstants.PATTERN_EXTENDER});
		    }
		}
	}
	
    /*****
    * Resolve a bundle with specified bundleId
    *  Install it if not exist in UI cache
    */ 
    public function resolveBundle(pluginId:String):void {
		if (pluginId == null)
			throw new Error("pluginId cannot be null"); //$NON-NLS-1$
		var task:Object = {pluginId:pluginId};	
		if(getBundle(pluginId) == null) {
		    task.loadType = BundleConstants.PATTERN_PLUGIN;
		    bundleInstallService.install(task);
		}
		bundleResolveService.resolve(task);
	}
    /******
    * Resolve required bundles for specified bundle
    * Get all required bundle ID, and resolve them.
    */ 	
	public function resolveRequiredBundles(pluginId:String):void {
		if (pluginId == null)
			throw new Error("pluginId cannot be null"); //$NON-NLS-1$
		var bundle:Bundle = getBundle(pluginId);	
		if (bundle == null)
			throw new Error(pluginId + " doesn't exist in UI"); //$NON-NLS-1$
		var array:Array = bundle.getRequires();
		if(array == null || array.length == 0) return;	
		for each(var id:String in array) {
		    resolveBundle(id);
		}
	}
	
    /*****
    * Start a bundle with specified bundleId
    *  Install previous bundles
    */ 
    public function startBundle(pluginId:String):void {
		if (pluginId == null)
			throw new Error("pluginId cannot be null"); //$NON-NLS-1$
		var task:Object = {pluginId:pluginId};	
		if(getBundle(pluginId) == null) {
		    task.loadType = BundleConstants.PATTERN_PLUGIN;
		    bundleInstallService.install(task);
		    bundleResolveService.resolve(task);
		} else if(getBundle(pluginId).getState() == Bundle.INSTALLED) {
		    bundleResolveService.resolve(task);
		}
		bundleStartService.start(task);
	}
		
	public function stopBundle(plugin:String):void {
	    // TODO
	}
    /*****
    * Start required bundles for specified bundle
    */ 
    public function startRequireBundles(pluginId:String):void {
		if (pluginId == null)
			throw new Error("pluginId cannot be null"); //$NON-NLS-1$
		var bundle:BundleImpl = getBundle(pluginId) as BundleImpl;	
		if (bundle == null)
			throw new Error(pluginId + " doesn't exist in UI"); //$NON-NLS-1$	
		var array:Array = bundle.getRequires();
		if(array == null || array.length == 0) return;	
		for each(var id:String in array) {
		    startBundle(id);
		}
	}		
	/* (non-Javadoc)
	 * @see org.eclipse.e4.internal.javascript.JSFramework#getBundles()
	 */
	public function getBundles():Array {
		return installOrderBundles.toArray();
	}

    public function getBundle(symbolicName:String):Bundle {
        return bundleCache[symbolicName] as Bundle;
    }
    
    /******
    * If the bundle is RESOLVED/STARTING/ACTIVE
    * then the bundle is resolved.
    */ 
    public function isResolved(bundleSymbolicName:String):Boolean {
        var bundle:BundleImpl = getBundle(bundleSymbolicName) as BundleImpl;
        if(bundle == null) { // bundle is not installed yet
            return false;
        }
        if(bundle.getState() == Bundle.RESOLVED || 
            bundle.getState() == Bundle.STARTING || 
            bundle.getState()== Bundle.ACTIVE  ||
            bundle.getState()== Bundle.UNINSTALLED) {
            return true;
        }
        return false;
    }
    
    /******
    * If the bundle is installed and all required bundles are resolved
    * then the bundle is resolveable. which means we can resolve it by loading its library
    */ 
    public function isResolveable(bundleSymbolicName:String):Boolean {
        var bundle:BundleImpl = getBundle(bundleSymbolicName) as BundleImpl;
        if(bundle == null) { // bundle is not installed yet
            return false;
        }
        if( bundle.getState() == Bundle.INSTALLED) {
            var requires:Array = bundle.getRequires();
            for each(var dependedId:String in requires) {
                if (isResolved(dependedId) == false) // required bundle is resolve or not
                    return false;
            }
            return true;
        }
        return false;
    }
    
    public function isStarted(bundleSymbolicName:String):Boolean {
        var bundle:BundleImpl = getBundle(bundleSymbolicName) as BundleImpl;
        if(bundle == null) { // bundle is not installed yet
            return false;
        }
        if(bundle.getState()== Bundle.ACTIVE) {
            return true;
        }
        return false;
    }
    
    /****
    * If the bundle is resolved, and all required bundles are started
    * also extender bundles are installed, then it is startable
    */ 
    public function isStartable(bundleSymbolicName:String):Boolean {
        var bundle:BundleImpl = getBundle(bundleSymbolicName) as BundleImpl;
        if(bundle == null) { // bundle is not installed yet
            return false;
        }
        if( bundle.getState() == Bundle.RESOLVED) {
            var requires:Array = bundle.getRequires();
            for each(var dependedId:String in requires) {
                if (isStarted(dependedId) == false) // required bundle is resolve or not
                    return false;
            }
            if(bundle.getPluginModel().getExtensionPoints().length == 0 && bundle.extendersReady == false) {
               bundle.extendersReady = true; 
            }
            if(bundle.extendersReady == false)
                return false;
            return true;
        }
        return false;
    }
    
    public function getDependentBundles(id:String):Array {
        // TODO
        return dependentBundles[id] as Array;
    }
    
	public function getBundleContext(jsBundle:Bundle):BundleContext {
		return new BundleContext(jsBundle, this);
	}

	public function shutdown():void {
		for (var key:String in bundleCache) {
		    var bundle:BundleImpl = bundleCache[key] as BundleImpl;
		    bundle.stop();
			bundle.uninstall();
		    delete bundleCache[key];
		}
		installOrderBundles = new ArrayCollection();
		dependentBundles = new Dictionary();
	}
}
}